/************************************************************************
                                                                     
   Xceed Ultimate ListBox for Silverlight                                                                                                                                            
   Copyright (C) 2010 Xceed Software Inc.    
                                                                     
   This program is provided to you under the terms of the GNU General Public  
   License version 2 as published by the Free Software Foundation. 
        
   This program is distributed in the hope that it will be useful, but
   WITHOUT ANY WARRANTY, without even the implied warranty of 
   MERCHANTABILITY or FITNESS FOR A PARTICULAR PURPOSE.  See the GNU
   General Public License for more details.

   You should have received a copy of the GNU General Public License along 
   with this program, if not, write to the Free Software Foundation, Inc., 
   51 Franklin Street, Fifth Floor, Boston, MA  02110-1301, USA.

   This program can be provided to you by Xceed Software Inc. under a
   proprietary commercial license agreement for use in non-Open Source
   projects. Visit Xceed.com to find the commercial edition of 
   Xceed Ultimate ListBox for Silverlight.                                    
                                                                      
 **********************************************************************/

using System;
using System.Collections.Generic;
using System.Diagnostics;
using System.Linq;

namespace Xceed.Silverlight.Data
{
  internal class StandardDataSourceEnumerator : DataSourceEnumeratorBase, ICachingEnumerator
  {
    #region Static Fields

    private const int BufferSizeRatio = 6;

    private static readonly Guid JumpMoveAndFetchCallId = DataUtils.CreateCallId();

    #endregion

    #region Constructor

    internal StandardDataSourceEnumerator(
      ReactiveCacheEnumerator childEnumerator,
      ReactiveDataPathEnumerator reactiveEnumerator,
      IDataHelper<DataPath> dataHelper,
      GroupHelper groupHelper )
      : base( childEnumerator,
          reactiveEnumerator,
          dataHelper,
          groupHelper )
    {
      this.ReactiveEnumerator = reactiveEnumerator;
      childEnumerator.OperationCompleted += new EventHandler<OperationCompletedEventArgs<DataPath>>( OnOperationCompleted );
    }

    #endregion

    #region CacheSize Property

    public int CacheSize
    {
      get
      {
        return this.CacheEnumerator.CacheSize;
      }
    }

    #endregion CacheSize Property

    #region CacheEnumerator Property

    private ReactiveCacheEnumerator CacheEnumerator
    {
      get
      {
        return ( ReactiveCacheEnumerator )this.ChildEnumeratorTyped;
      }
    }

    #endregion CacheEnumerator Property

    #region ReactiveEnumerator Property

    private ReactiveDataPathEnumerator ReactiveEnumerator
    {
      get;
      set;
    }

    #endregion ReactiveEnumerator Property

    public override SourcePosition CreateGroupFetchSourcePosition( DataPath groupPath, StartPosition fetchStartPosition )
    {
      if( !this.ReactiveEnumerator.DataItemFactory.IsDummy( groupPath ) )
        return base.CreateGroupFetchSourcePosition( groupPath, fetchStartPosition );

      int groupDepth = groupPath.Depth;

      Func<DataPath, SourcePosition> sortPositionCreator =
        ( path ) =>
        {
          if( path.Depth < groupDepth )
            throw new DataInternalException();

          if( path.Depth > groupDepth )
          {
            path = path.CreateAncestorPath( groupDepth );
          }

          return base.CreateGroupFetchSourcePosition( path, fetchStartPosition );
        };

      DependentPosition<DataPath> dependentPosition =
        new DependentPosition<DataPath>( groupPath, sortPositionCreator );

      return dependentPosition;
    }

    protected override void ExpansionChanging(
      object sender,
      ExpansionChangedEventArgs e )
    {
      this.CacheEnumerator.ExpansionChanging( sender, e );
      base.ExpansionChanging( sender, e );
    }

    protected override void OnHasPendingRequestsChanged( object sender, EventArgs e )
    {
      if( !this.ReactiveEnumerator.HasPendingRequests )
      {
        this.AdjustIndexRelativePosition();
      }
      base.OnHasPendingRequestsChanged( sender, e );
    }

    protected override void AdjustIndexRelativePosition()
    {
      if( !this.HasPendingRequests )
      {
        base.AdjustIndexRelativePosition();
      }
    }

    protected override void HandleInconsistentCountData( ref RelativeOffset currentOffset, ref EstimateItemCount itemCount )
    {
      Debug.Assert( false, "There is inconsistency in the counts. Either the request is faulty, or the cache assume invalid data." );
      currentOffset = currentOffset.ToEstimate();
    }

    private int FindDataPath( DataPath path, IEnumerable<DataPath> collection )
    {
      if( collection != null )
      {
        IList<DataPath> list = collection as IList<DataPath>;

        if( list != null )
          return list.IndexOf( path );

        int indexOf = 0;

        foreach( DataPath item in collection )
        {
          if( object.Equals( item, path ) )
            return indexOf;

          indexOf++;
        }
      }

      return -1;
    }

    private void OnOperationCompleted( object sender, OperationCompletedEventArgs<DataPath> e )
    {
      if( e.Action == OperationCompletedAction.Items )
      {
        this.OnItemsOperationCompleted( e );
      }
      else
      {
        this.OnStatResultOperationCompleted( e );
      }
    }

    private void OnStatResultOperationCompleted( OperationCompletedEventArgs<DataPath> e )
    {
      this.RaiseDataChanged( new DataChangedEventArgs( DataChangedAction.StatLoaded, e.StatParameters, e.RealStatResults, e.DummyStatResults ) );
    }

    private void OnItemsOperationCompleted( OperationCompletedEventArgs<DataPath> e )
    {

      RelativeOffset currentOffset = null;
      DataPath removedCurrent = null;
      bool updateItems = false;
      List<Tuple<object, object>> itemsLoaded = new List<Tuple<object, object>>();

      if( e.Replacements != null )
      {
        foreach( Tuple<DataPath, DataPath> replacement in e.Replacements )
        {
          this.HandleCurrentReplace( replacement.Item1, replacement.Item2 );

          if( this.CacheEnumerator.CachedItems.Contains( replacement.Item2 ) )
          {
            itemsLoaded.Add( new Tuple<object, object>( replacement.Item1, replacement.Item2 ) );
          }
        }
      }

      if( e.Deletions != null )
      {
        updateItems = updateItems || ( e.Deletions.Count > 0 );
        SourcePosition notUsed;
        foreach( DataPath deletedPath in e.Deletions )
        {
          if( this.HandleCurrentRemove( deletedPath, e.Operation.EndPositionObject, out notUsed, out currentOffset ) )
          {
            removedCurrent = deletedPath;
            break;
          }
        }
      }

      if( this.CurrentPosition is ItemPosition )
      {
        CacheEnumerator<DataPath> cache = this.CacheEnumerator;
        DataPath path = ( DataPath )( ( ItemPosition )this.CurrentPosition ).Item;

        RelativeOffset endPositionOffset = this.HandleUnsupportedEstimateIndex( e.Operation.EndPositionOffset );
        if( endPositionOffset != null )
        {
          int pathOffset = this.FindDataPath( path, e.Operation.FetchedItems );

          if( pathOffset >= 0 )
          {
            long referenceOffset = ( pathOffset * e.Operation.GetFetchUnit() ) - e.Operation.GetFetchDisplacement();

            currentOffset = DataUtils.CalculateRelativeOffset(
              endPositionOffset,
              referenceOffset );
          }
        }

        if( currentOffset == null )
        {
          RelativeOffset cacheFirstItemOffset = this.HandleUnsupportedEstimateIndex( cache.FirstItemOffset );
          if( cacheFirstItemOffset != null )
          {
            int pathOffset = this.FindDataPath( path, cache.CachedItems );

            if( pathOffset >= 0 )
            {
              currentOffset = DataUtils.CalculateRelativeOffset(
                cacheFirstItemOffset,
                pathOffset );
            }
          }
        }
      }

      EstimateItemCount newCount = null;
      if( e.Operation.TotalCount != null )
      {
        newCount = new EstimateItemCount( e.Operation.TotalCount, false );
      }

      if( currentOffset != null && newCount == null && this.ItemCountEstimate.IsBasedOnOffset )
      {
        newCount = this.ItemCountEstimate;
        newCount = DataSourceEnumeratorBase.CalculateNewCountBasedOnOffsets( this.CurrentOffset, currentOffset, newCount );
      }

      this.SetCountValues( currentOffset, newCount );

      if( removedCurrent != null )
      {
        this.RaiseDataRemoved( removedCurrent, e.Operation.EndPositionObject, e.Operation.EndPositionObject );
      }
      else if( itemsLoaded.Count > 0 || updateItems )
      {
        // Rise ItemsLoaded events.
        this.RaiseDataChanged( new DataChangedEventArgs( DataChangedAction.ItemsLoaded, itemsLoaded, updateItems ) );
      }
    }


    #region SetCacheSize Methods

    public void SetCacheSize( int size )
    {
      this.CacheEnumerator.SetCacheSize( size, this.CurrentPosition );
    }

    public void SetCacheSize( int size, double topTrimRatio )
    {
      this.CacheEnumerator.SetCacheSize( size, topTrimRatio );
    }

    #endregion

    #region BeginJumpMoveAndFetch Methods

    public override JumpMoveAndFetchAsyncResult BeginJumpMoveAndFetch( JumpMoveAndFetchParameters parameters, AsyncCallback userCallback, object asyncState )
    {
      this.CheckMoveAndFetchParameters( parameters.ExpectedMoveCount, parameters.ExpectedFetchCount );

      JumpMoveAndFetchAsyncResult asyncResult = new JumpMoveAndFetchAsyncResult( StandardDataSourceEnumerator.JumpMoveAndFetchCallId, userCallback, asyncState, parameters );

      //If a move-only operation is made, always get the last item anyway in 
      //order to always have the "current position" in cache.
      if( parameters.ExpectedFetchCount == 0 )
      {
        parameters = parameters.CreateWithNewFetchCount(
                       DataUtils.GetUnit( parameters.ExpectedMoveCount ) );
      }

      asyncResult.Start(
        ( callback, state ) => this.BaseBeginJumpMoveAndFetch( parameters, callback, state ),
        null );

      return asyncResult;
    }

    public override JumpMoveAndFetchResult<DataPath> EndJumpMoveAndFetch( JumpMoveAndFetchAsyncResult jumpMoveAndFetchAsyncResult )
    {
      jumpMoveAndFetchAsyncResult.ValidateCallEnd( StandardDataSourceEnumerator.JumpMoveAndFetchCallId );

      JumpMoveAndFetchResult<DataPath> result = base.EndJumpMoveAndFetch( jumpMoveAndFetchAsyncResult.GetLastAsyncResult<JumpMoveAndFetchAsyncResult>() );

      //Remove the "extra fetch" if the expected fetch count was zero.
      //See ReactiveWithCacheEnumerator.BeginJumpMoveAndFetch)
      if( ( jumpMoveAndFetchAsyncResult.ExpectedFetchCount == 0 )
        && ( result.FetchedItems != null && result.FetchedItems.Count > 0 ) )
      {
        result = new JumpMoveAndFetchResult<DataPath>(
                       jumpMoveAndFetchAsyncResult,
                       result.ActualMoveCount,
                       new List<DataPath>(),
                       result.Result,
                       result.EndPositionObject,
                       result.EndPositionOffset,
                       result.TotalCount );
      }
      else
      {
        result = new JumpMoveAndFetchResult<DataPath>(
                       jumpMoveAndFetchAsyncResult,
                       result.ActualMoveCount,
                       result.FetchedItems,
                       result.Result,
                       result.EndPositionObject,
                       result.EndPositionOffset,
                       result.TotalCount );
      }
      return result;
    }

    private IAsyncResult BaseBeginJumpMoveAndFetch( JumpMoveAndFetchParameters parameters, AsyncCallback userCallback, object asyncState )
    {
      return base.BeginJumpMoveAndFetch( parameters, userCallback, asyncState );
    }

    #endregion

    #region IDisposable Members

    private bool m_disposed; //false

    protected override void Dispose( bool disposing )
    {
      if( !m_disposed )
      {
        this.CacheEnumerator.OperationCompleted -= new EventHandler<OperationCompletedEventArgs<DataPath>>( OnOperationCompleted );

        m_disposed = true;

        base.Dispose( disposing );
      }
    }

    #endregion
  }
}
